﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.Objects.DataClasses;
using System.Linq.Expressions;
using System.Reflection;
using System.Data.Objects;

namespace IRC.IRepairClouds.DataLayer
{
    public static class EntityExtensions
    {
        public static TSource GetSingle<TSource>(this IQueryable<TSource> source)
        {
            return GetSingle(source, false);
        }

        public static TSource GetSingle<TSource>(this IQueryable<TSource> source, bool throwIfNotExists)
        {
            source.ThrowIfNull("source");
            var res = source.ToList();
            if (res.Count == 1)
                return res[0];

            if (res.Count > 1)
                throw new NotSupportedException("More than one entity found");

            if (throwIfNotExists)
                throw new EntityNotExistsException();
            else
                return default(TSource);
        }

        public static TSource GetSingle<TSource>(this IEnumerable<TSource> source)
        {
            return GetSingle(source, false);
        }

        public static TSource GetSingle<TSource>(this IEnumerable<TSource> source, bool throwIfNotExists)
        {
            source.ThrowIfNull("source");
            var res = source.ToList();
            if (res.Count == 1)
                return res[0];

            if (res.Count > 1)
                throw new NotSupportedException("More than one entity found");

            if (throwIfNotExists)
                throw new EntityNotExistsException();
            else
                return default(TSource);
        }



        public static HashSet<TKey> ToHashSet<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector)
        {
            return ToHashSet(source, keySelector, null);
        }

        public static HashSet<TKey> ToHashSet<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, IEqualityComparer<TKey> comparer)
        {
            source.ThrowIfNull("source");
            keySelector.ThrowIfNull("keySelector");
            HashSet<TKey> hashSet = new HashSet<TKey>(comparer);
            foreach (TSource local in source)
                hashSet.Add(keySelector(local));
            return hashSet;
        }

        public static HashSet<TSource> ToHashSet<TSource>(this IEnumerable<TSource> source)
        {
            return ToHashSet(source, null);
        }

        public static HashSet<TSource> ToHashSet<TSource>(this IEnumerable<TSource> source, IEqualityComparer<TSource> comparer)
        {
            source.ThrowIfNull("source");
            HashSet<TSource> hashSet = new HashSet<TSource>(comparer);
            foreach (TSource local in source)
                hashSet.Add(local);
            return hashSet;
        }

        public static global::System.Data.Objects.DataClasses.EntityCollection<T> Loaded<T>(this global::System.Data.Objects.DataClasses.EntityCollection<T> value) where T : global::System.Data.Objects.DataClasses.EntityObject
        {
            if (value != null && !value.IsLoaded)
                value.Load();
            return value;
        }

        public static T Loaded<T>(this global::System.Data.Objects.DataClasses.EntityReference<T> value) where T : global::System.Data.Objects.DataClasses.EntityObject
        {
            if (!value.IsLoaded)
                value.Load();
            return value.Value;
        }

        public static IEnumerable<T> Detach<T>(this IEnumerable<T> col, global::System.Data.Objects.ObjectContext context) where T : global::System.Data.Objects.DataClasses.EntityObject
        {
            foreach (var i in col) {
                context.Detach(i);
                yield return i;
            }
        }

        public static IOrderedQueryable<TSource> OrderByX<TSource, TKey>(this IQueryable<TSource> source, bool ascending, Expression<Func<TSource, TKey>> keySelector)
        {
            if (ascending)
                return source.OrderBy(keySelector);
            return source.OrderByDescending(keySelector);
        }
        public static IOrderedQueryable<TSource> OrderByX<TSource, TKey>(this IQueryable<TSource> source, bool ascending, Expression<Func<TSource, TKey>> keySelector, IComparer<TKey> comparer)
        {
            if (ascending)
                return source.OrderBy(keySelector, comparer);
            return source.OrderByDescending(keySelector, comparer);
        }


        internal static TKey GetKeyValue<TEntity, TKey>(this EntityReference<TEntity> reference, Expression<Func<TEntity, TKey>> keySelector) where TEntity : class, System.Data.Objects.DataClasses.IEntityWithRelationships
        {
            reference.ThrowIfNull("reference");
            keySelector.ThrowIfNull("keySelector");
            if (reference.EntityKey == null)
                throw new ArgumentOutOfRangeException("reference");
            var me = keySelector.Body as MemberExpression;
            if (me == null)
                throw new ArgumentOutOfRangeException("keySelector", "Is not MemberExpression");
            if (me.Member.MemberType != MemberTypes.Property)
                throw new ArgumentOutOfRangeException("keySelector", "Is not Property");
            if (!(me.Expression is ParameterExpression))
                throw new ArgumentOutOfRangeException("keySelector", "Is not Property");

            var attr = me.Member.GetCustomAttributes(typeof(EdmScalarPropertyAttribute), false);
            if (attr == null || attr.Length != 1 || ((EdmScalarPropertyAttribute)attr[0]).EntityKeyProperty == false)
                throw new ArgumentOutOfRangeException("keySelector", "Is not EntityKeyProperty");

            return (TKey)reference.EntityKey.EntityKeyValues.Where(i => i.Key == me.Member.Name).Select(i => i.Value).Single();
        }

        public static bool HasKey<TEntity>(this EntityReference<TEntity> reference) where TEntity : class, System.Data.Objects.DataClasses.IEntityWithRelationships
        {
            reference.ThrowIfNull("reference");
            return reference.EntityKey != null;
        }

        public static bool EqualsKey<T>(this T item1, T item2) where T : global::System.Data.Objects.DataClasses.EntityObject
        {
            return Entity.EqualsKey(item1, item2);
        }

        public static ObjectQuery<T> Include<T, TKey>(this ObjectQuery<T> query, Expression<Func<T, TKey>> itemSelector)
        {
            query.ThrowIfNull("query");
            itemSelector.ThrowIfNull("itemSelector");
            string path = null;

            Expression exp = itemSelector.Body;
            MemberExpression me = exp as MemberExpression;
            while (me != null) {
                if (me.Member.MemberType != MemberTypes.Property)
                    throw new ArgumentOutOfRangeException("itemSelector", "Is not Property");
                if (path != null)
                    path = "." + path;
                path = me.Member.Name + path;
                exp = me.Expression;
                me = exp as MemberExpression;
            }
            if (!(exp is ParameterExpression))
                throw new ArgumentOutOfRangeException("itemSelector", "Is not Property");

            return query.Include(path);
        }


        public class SortFieldHelper
        {
            public static SortFieldHelper<TSource, TKey> Create<TSource, TKey>(string fieldName, Expression<Func<TSource, TKey>> keySelector)
            {
                return new SortFieldHelper<TSource, TKey>(fieldName, keySelector);
            }
            public static SortFieldHelper<TSource, TKey> Create<TSource, TKey>(string fieldName, Expression<Func<TSource, TKey>> keySelector, IComparer<TKey> comparer)
            {
                return new SortFieldHelper<TSource, TKey>(fieldName, keySelector, comparer);
            }
        }

        public class SortFieldHelper<TSource> : SortFieldHelper
        {
            public string FieldName { get; private set; }
            public Expression KeySelector { get; private set; }
            public object Comparer { get; private set; }

            public SortFieldHelper(string fieldName, Expression keySelector)
                :this(fieldName, keySelector, null)
            {
            }

            public SortFieldHelper(string fieldName, Expression keySelector, object comparer)
            {
                FieldName = fieldName;
                KeySelector = keySelector;
                Comparer = comparer;
            }
        }

        public class SortFieldHelper<TSource, TKey> : SortFieldHelper<TSource>
        {
            public SortFieldHelper(string fieldName, Expression<Func<TSource, TKey>> keySelector)
                : base(fieldName, keySelector)
            {
            }

            public SortFieldHelper(string fieldName, Expression<Func<TSource, TKey>> keySelector, IComparer<TKey> comparer)
                : base(fieldName, keySelector, comparer)
            {
            }
        }

        public static SortFieldHelper<TSource, TKey> CreateSortField<TSource, TKey>(this IQueryable<TSource> source, string fieldName, Expression<Func<TSource, TKey>> keySelector)
        {
            return new SortFieldHelper<TSource, TKey>(fieldName, keySelector);
        }


        private static IQueryable<TSource> OrderByXSimple<TSource, TKey>(this IQueryable<TSource> source, SortOrder sortOrder, Expression<Func<TSource, TKey>> keySelector)
        {
            if (sortOrder == SortOrder.Ascending)
                return source.OrderBy(keySelector);
            if (sortOrder == SortOrder.Descending)
                return source.OrderByDescending(keySelector);
            return source;
        }
        public static IQueryable<TSource> OrderByXComparer<TSource, TKey>(this IQueryable<TSource> source, SortOrder sortOrder, Expression<Func<TSource, TKey>> keySelector, IComparer<TKey> comparer)
        {
            if (sortOrder == SortOrder.Ascending)
                return source.OrderBy(keySelector, comparer);
            if (sortOrder == SortOrder.Descending)
                return source.OrderByDescending(keySelector, comparer);
            return source;
        }


        public static IQueryable<TSource> Sort<TSource>(this IQueryable<TSource> source, SortField sortField, params SortFieldHelper<TSource>[] fields)
        {
            if (sortField != null)
                return Sort<TSource>(source, sortField.FieldName, sortField.SortOrder, fields);
            return source;
            
        }

        public static IQueryable<TSource> Sort<TSource>(this IQueryable<TSource> source, string fieldName, SortOrder order, params SortFieldHelper<TSource>[] fields)
        {
            for (int i = 0; i < fields.Length; i++) {
                var f = fields[i];
                if (f.FieldName == fieldName) {
                    var t = f.GetType();
                    var tSource = typeof(TSource);
                    var tKey = t.GetGenericArguments()[1];
                    MethodInfo miOx;
                    if (f.Comparer == null) {
                        miOx = typeof(EntityExtensions).GetMethod("OrderByXSimple", BindingFlags.Static | BindingFlags.NonPublic).MakeGenericMethod(tSource, tKey);
                        return (IQueryable<TSource>)miOx.Invoke(null, new object[] { source, order, f.KeySelector });
                    }
                    else {
                        miOx = typeof(EntityExtensions).GetMethod("OrderByXComparer", BindingFlags.Static | BindingFlags.NonPublic).MakeGenericMethod(tSource, tKey);
                        return (IQueryable<TSource>)miOx.Invoke(null, new object[] { source, order, f.KeySelector, f.Comparer });
                    }
                }
            }
            return source;
        }
    }
}
